﻿Imports System
Imports System.Collections.Generic
Imports System.Reflection
Imports System.Runtime.InteropServices
Imports System.Workflow.Runtime

Namespace System.Workflow.Activities
    Friend NotInheritable Class DefaultCorrelationProvider
        Implements ICorrelationProvider
        ' Methods
        Friend Sub New(ByVal interfaceType As Type)
            Me.interfaceType = interfaceType
        End Sub

        Private Function GetCorrelationParameterAttributes(ByVal type As Type) As Object()
            Return type.GetCustomAttributes(GetType(CorrelationParameterAttribute), True)
        End Function

        Private Function GetCorrelationProperties(ByVal interfaceType As Type, ByVal methodName As String) As CorrelationPropertyValue()
            Dim valueArray As CorrelationPropertyValue() = Nothing
            If (interfaceType.GetCustomAttributes(GetType(ExternalDataExchangeAttribute), True).Length = 0) Then
                Throw New InvalidOperationException(SR.GetString("Error_ExternalDataExchangeException", New Object() { interfaceType.AssemblyQualifiedName }))
            End If
            Dim list As New List([Of] Object)
            list.AddRange(Me.GetCorrelationParameterAttributes(interfaceType))
            If (list.Count = 0) Then
                Throw New InvalidOperationException(SR.GetString("Error_CorrelationParameterException", New Object() { interfaceType.AssemblyQualifiedName }))
            End If
            valueArray = New CorrelationPropertyValue(list.Count  - 1) {}
            Dim correlationAliases As Dictionary([Of] String, CorrelationAliasAttribute) = Nothing
            Dim methodInfo As MethodInfo = Nothing
            Me.GetMethodInfo(interfaceType, methodName, methodInfo, correlationAliases)
            If (methodInfo Is Nothing) Then
                Throw New MissingMethodException(interfaceType.AssemblyQualifiedName, methodName)
            End If
            Dim parameters As ParameterInfo() = methodInfo.GetParameters
            Dim num As Integer = 0
            Dim attribute As CorrelationParameterAttribute
            For Each attribute In list
                Dim name As String = attribute.Name
                Dim attribute2 As CorrelationAliasAttribute = Me.GetMatchingCorrelationAlias(attribute, correlationAliases, (list.Count = 1))
                If (Not attribute2 Is Nothing) Then
                    name = attribute2.Path
                End If
                Dim value2 As CorrelationPropertyValue = Me.GetCorrelationProperty(parameters, attribute.Name, name)
                If (value2 Is Nothing) Then
                    Throw New InvalidOperationException(SR.GetString("Error_CorrelationParameterException", New Object() { interfaceType.AssemblyQualifiedName, attribute.Name, methodName }))
                End If
                valueArray(num++) = value2
            Next
            Return valueArray
        End Function

        Private Function GetCorrelationProperty(ByVal parameters As ParameterInfo(), ByVal propertyName As String, ByVal location As String) As CorrelationPropertyValue
            Dim strArray As String() = location.Split(New Char() { "."c })
            If (((strArray.Length = 1) AndAlso (parameters.Length = 2)) AndAlso GetType(ExternalDataEventArgs).IsAssignableFrom(parameters(1).ParameterType)) Then
                Dim str As String = ("e." & location)
                Return Me.GetCorrelationProperty(parameters, propertyName, "e", str)
            End If
            Dim parameterName As String = strArray(0)
            Return Me.GetCorrelationProperty(parameters, propertyName, parameterName, location)
        End Function

        Private Function GetCorrelationProperty(ByVal parameters As ParameterInfo(), ByVal propertyName As String, ByVal parameterName As String, ByVal location As String) As CorrelationPropertyValue
            Dim i As Integer = 0
            Do While ((Not parameters Is Nothing) AndAlso (i < parameters.Length))
                Dim info As ParameterInfo = parameters(i)
                If (info.Name = parameterName) Then
                    Return New CorrelationPropertyValue(propertyName, location, info.Position)
                End If
                i += 1
            Loop
            Return Nothing
        End Function

        Private Function GetMatchingCorrelationAlias(ByVal paramAttribute As CorrelationParameterAttribute, ByVal correlationAliases As Dictionary([Of] String, CorrelationAliasAttribute), ByVal defaultParameter As Boolean) As CorrelationAliasAttribute
            Dim attribute As CorrelationAliasAttribute = Nothing
            If (correlationAliases Is Nothing) Then
                Return Nothing
            End If
            If (Not defaultParameter OrElse Not correlationAliases.TryGetValue("", attribute)) Then
                correlationAliases.TryGetValue(paramAttribute.Name, attribute)
            End If
            Return attribute
        End Function

        Private Sub GetMethodInfo(ByVal interfaceType As Type, ByVal methodName As String, <Out> ByRef methodInfo As MethodInfo, <Out> ByRef correlationAliases As Dictionary([Of] String, CorrelationAliasAttribute))
            correlationAliases = New Dictionary([Of] String, CorrelationAliasAttribute)
            Dim customAttributes As Object() = Nothing
            methodInfo = Nothing
            Dim bindingAttr As BindingFlags = (BindingFlags.NonPublic Or (BindingFlags.Public Or BindingFlags.Instance))
            Dim info As EventInfo = interfaceType.GetEvent(methodName, bindingAttr)
            If (Not info Is Nothing) Then
                customAttributes = info.GetCustomAttributes(GetType(CorrelationAliasAttribute), True)
                If ((customAttributes Is Nothing) OrElse (customAttributes.Length = 0)) Then
                    customAttributes = info.EventHandlerType.GetCustomAttributes(GetType(CorrelationAliasAttribute), True)
                End If
                Dim methods As MethodInfo() = info.EventHandlerType.GetMethods
                methodInfo = methods(0)
            Else
                methodInfo = interfaceType.GetMethod(methodName, bindingAttr)
                If (methodInfo Is Nothing) Then
                    Throw New MissingMethodException(interfaceType.AssemblyQualifiedName, methodName)
                End If
                customAttributes = methodInfo.GetCustomAttributes(GetType(CorrelationAliasAttribute), True)
            End If
            Dim attribute As CorrelationAliasAttribute
            For Each attribute In customAttributes
                If ((customAttributes.Length > 1) AndAlso (attribute.Name Is Nothing)) Then
                    Throw New ArgumentNullException("ParameterName")
                End If
                correlationAliases.Add(If((attribute.Name Is Nothing), "", attribute.Name), attribute)
            Next
        End Sub

        Private Function System.Workflow.Activities.ICorrelationProvider.IsInitializingMember(ByVal interfaceType As Type, ByVal memberName As String, ByVal methodArgs As Object()) As Boolean Implements ICorrelationProvider.IsInitializingMember
            Return Me.InitializerCorrelationPropertys.ContainsKey(memberName)
        End Function

        Private Function System.Workflow.Activities.ICorrelationProvider.ResolveCorrelationPropertyValues(ByVal interfaceType As Type, ByVal methodName As String, ByVal methodArgs As Object(), ByVal provideInitializerTokens As Boolean) As ICollection([Of] CorrelationProperty) Implements ICorrelationProvider.ResolveCorrelationPropertyValues
            Dim correlationProperties As CorrelationPropertyValue() = Nothing
            If ((methodArgs Is Nothing) OrElse provideInitializerTokens) Then
                Return Nothing
            End If
            Me.cachedCorrelationProperties.TryGetValue(methodName, correlationProperties)
            If (correlationProperties Is Nothing) Then
                SyncLock Me.cachedCorrelationPropertiesSync
                    Me.cachedCorrelationProperties.TryGetValue(methodName, correlationProperties)
                    If (correlationProperties Is Nothing) Then
                        correlationProperties = Me.GetCorrelationProperties(interfaceType, methodName)
                        Me.cachedCorrelationProperties.Add(methodName, correlationProperties)
                    End If
                End SyncLock
            End If
            Dim list As New List([Of] CorrelationProperty)
            Dim i As Integer
            For i = 0 To correlationProperties.Length - 1
                list.Add(New CorrelationProperty(correlationProperties(i).Name, correlationProperties(i).GetValue(methodArgs)))
            Next i
            Return list
        End Function


        ' Properties
        Private ReadOnly Property InitializerCorrelationPropertys As Dictionary([Of] String, Boolean)
            Get
                If (Me.initializerCorrelationPropertys Is Nothing) Then
                    SyncLock Me.initializerCorrelationPropertysSync
                        If (Me.initializerCorrelationPropertys Is Nothing) Then
                            Dim dictionary As New Dictionary([Of] String, Boolean)
                            Dim info As EventInfo
                            For Each info In Me.interfaceType.GetEvents
                                If (info.GetCustomAttributes(GetType(CorrelationInitializerAttribute), True).Length > 0) Then
                                    dictionary.Add(info.Name, True)
                                End If
                            Next
                            Dim info2 As MethodInfo
                            For Each info2 In Me.interfaceType.GetMethods
                                If (info2.GetCustomAttributes(GetType(CorrelationInitializerAttribute), True).Length > 0) Then
                                    dictionary.Add(info2.Name, False)
                                End If
                            Next
                            Me.initializerCorrelationPropertys = dictionary
                        End If
                    End SyncLock
                End If
                Return Me.initializerCorrelationPropertys
            End Get
        End Property


        ' Fields
        Private cachedCorrelationProperties As Dictionary([Of] String, CorrelationPropertyValue()) = New Dictionary([Of] String, CorrelationPropertyValue())
        Private cachedCorrelationPropertiesSync As Object = New Object
        Private initializerCorrelationPropertys As Dictionary([Of] String, Boolean)
        Private initializerCorrelationPropertysSync As Object = New Object
        Private interfaceType As Type
    End Class
End Namespace

